package NES;

use strict;
use Data::Dumper;
use NES::M6502;
use NES::Cartridge;
use NES::Memory;
use NES::PPU;
use SDL;
use SDLx::App;

use constant {
    # ciclos por scanline
    TARGET_CYCLES => 114, # FIXME: 114 * 3 ???
};

sub new {
    my $class = shift;

    my $self = bless {
        memory => undef,
        cart   => undef,
        6502   => undef,
        ppu    => undef,
        screen => undef,
    }, $class;

    return $self;
}

sub load_file {
    my $self     = shift;
    my ( $path ) = @_;

    die $! unless -f $path;

    $self->{memory} = NES::Memory->new;

    $self->{cart}   = NES::Cartridge->new( file => $path, memory => $self->{memory} );

    die unless $self->{cart};

    $self->{6502} = NES::M6502->new(
        read  => sub { return $self->read_memory(@_) },
        write => sub { return $self->write_memory(@_) },
    );

    $self->{ppu} = NES::PPU->new(
        read_vram     => sub { return $self->{memory}->read_vram(@_) },
        write_vram    => sub { return $self->{memory}->write_vram(@_) },
        aquire_screen => sub { return $self->aquire_screen },
        mirroring     => $self->{cart}->get_mirroring,
    );
}

sub loop {
    my $self = shift;

    $self->launch_sdl( w => 512, h => 480, title => $self->{cart}->get_file );

    my $cycles = 0;
    while( 1 ) {
        while( $cycles <= TARGET_CYCLES ) {
            my $opcode = $self->{6502}->fetch;
            $cycles += $self->{6502}->execute( $opcode );
        }
        $cycles -= TARGET_CYCLES;
        $self->{ppu}->run_scanline;

        # nmi
        if( $self->{ppu}->want_nmi ) {
            $self->{ppu}->toggle_nmi;
            $self->{6502}->handle_nmi;
        }
    }
}

sub launch_sdl {
    my $self = shift;
    my %args = @_;

    $self->{screen} = SDLx::App->new(
        width  => ( $args{w} || 512 ),
        height => ( $args{h} || 480 ),
        title  => ( sprintf "perlnes%s", $args{title} ? ' - '.$args{title} : '' ),
        depth  => 32,
    );
}

sub aquire_screen {
    my $self = shift;

    return $self->{screen};
}

sub read_memory {
    my $self = shift;
    my ( $address ) = @_;

    $address = ( $address & 0xffff );

    # ram
    if( $address < 0x2000 ) {
        return $self->{memory}->read( $address & 0x07ff );
    }

    # ppu
    elsif( $address < 0x4000 ) {
        return $self->{ppu}->read( $address );
        # return $self->{ppu}->read( ($address - 0x2000) & 0xffff );
    }

    # FIXME: PRG-ROM? segun nes_emu.txt:2264 esta region es un mirror de (0..0x3fff)
    else {
        return $self->{memory}->read( $address );
    }
}

sub write_memory {
    my $self = shift;
    my ( $address, $value ) = @_;

    $address = ( $address & 0xffff );
    $value   = ( $value & 0x00ff );

    # ram
    if( $address < 0x2000 ) {
        $self->{memory}->write( $address & 0x07ff, $value );
    }

    # ppu
    elsif( $address < 0x4000 ) {
        $self->{ppu}->write( $address, $value );
        # $self->{ppu}->write( ($address - 0x2000) & 0xffff, $value );
    }

    # FIXME: PRG-ROM? segun nes_emu.txt:2264 esta region es un mirror de (0..0x3fff)
    else {
        $self->{memory}->write( $address, $value );
    }
}

1;